﻿using System;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;

using SharpGL.SceneGraph.Effects;

namespace SharpGL.SceneGraph
{
    /// <summary>
    /// The base class for all elements in a scene. Anything in
    /// the scene tree is a Scene Element. Scene elements can
    /// have children but are very lacking in functionality -
    /// implement.
    /// </summary>
    [TypeConverter(typeof(System.ComponentModel.ExpandableObjectConverter))]
    [DataContract(IsReference = true, Name = "SceneElement", Namespace = "SharpGL.SceneGraph")]
    public abstract partial class SceneElement : INotifyPropertyChanged, INotifyContextChanged, INotifySceneTreeChanged
    {
        #region Private Consts

        private static readonly SceneElement defaultParent = null;
        private static readonly string defaultName = "Scene element";
        private static readonly bool defaultEnable = true;

        #endregion Private Consts

        #region Private Fields

        /// <summary>
        /// The parent scene element.
        /// </summary>
        [DataMember()]
        private SceneElement parent;

        /// <summary>
        /// The name.
        /// </summary>
        [DataMember()]
        private string name;

        /// <summary>
        /// A value indicating whether this instance is enabled.
        /// </summary>
        [DataMember()]
        private bool enable;

        /// <summary>
        /// The children of the element.
        /// </summary>
        [DataMember()]
        private ObservableCollection<SceneElement> children;

        /// <summary>
        /// The effects.
        /// </summary>
        [DataMember()]
        private ObservableCollection<Effect> effects;

        #endregion Private Fields

        #region Private Methods

        private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (SceneElement sceneElement in e.OldItems)
                {
                    NotifySceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.RemoveElement, sceneElement));
                    sceneElement.Parent = null;
                    sceneElement.SceneTreeChanged -= new SceneTreeChangeEventHandler(OnSceneTreeChanged);
                    sceneElement.ContextChanged -= new ContextChangeEventHandler(OnContextChanged);
                    sceneElement.PropertyChanged -= new PropertyChangedEventHandler(OnPropertyChanged);
                }
            }

            if (e.NewItems != null)
            {
                foreach (SceneElement sceneElement in e.NewItems)
                {
                    //  Throw an exception if the child already has a parent.
                    if (sceneElement.Parent != null)
                        throw new Exception("Can't add the child element '" +
                                            sceneElement.Name +
                                            "' - it is already in the Scene Tree.");

                    sceneElement.SceneTreeChanged += new SceneTreeChangeEventHandler(OnSceneTreeChanged);
                    sceneElement.ContextChanged += new ContextChangeEventHandler(OnContextChanged);
                    sceneElement.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
                    sceneElement.Parent = this;
                    NotifySceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.AddElement, sceneElement));
                }
            }
        }

        private void OnContextChanged(object sender, ContextChangeEventArgs e)
        {
            NotifyContextChanged(sender, e);
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext ctx)
        {
            OnDeserialized();
        }

        [OnDeserializing]
        private void OnDeserializing(StreamingContext ctx)
        {
            OnDeserializing();
        }

        private void OnEffectsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (Effect effect in e.OldItems)
                {
                    NotifySceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.RemoveEffect, effect));
                    if (effect is INotifyContextChanged)
                        ((INotifyContextChanged)effect).ContextChanged -= new ContextChangeEventHandler(OnContextChanged);
                    effect.PropertyChanged -= new PropertyChangedEventHandler(OnPropertyChanged);
                }
            }

            if (e.NewItems != null)
            {
                foreach (Effect effect in e.NewItems)
                {
                    if (effect is INotifyContextChanged)
                        ((INotifyContextChanged)effect).ContextChanged += new ContextChangeEventHandler(OnContextChanged);
                    effect.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
                    NotifySceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.AddEffect, effect));
                }
            }
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender is SceneElement)
                NotifyPropertyChanged("Childrens[" + sender.GetType() + "]" + "." + e.PropertyName);
            else if (sender is Effect)
                NotifyPropertyChanged("Effects[" + sender.GetType() + "]" + "." + e.PropertyName);
            else
                NotifyPropertyChanged(sender.GetType() + "." + e.PropertyName);
        }

        /// <summary>
        /// Called when scene tree changed.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A SceneTreeChangeEventArgs that contains the event data.</param>
        private void OnSceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            NotifySceneTreeChanged(sender, e);
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Call this function to do the SceneTreeChanged event.
        /// </summary>
        protected virtual void NotifySceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            var handler = SceneTreeChanged;
            if (handler != null)
                handler(sender, e);
        }

        /// <summary>
        /// Call this function to do the ContextChanged event.
        /// </summary>
        protected virtual void NotifyContextChanged(object sender, ContextChangeEventArgs e)
        {
            var handler = ContextChanged;
            if (handler != null)
                handler(sender, e);
        }

        /// <summary>
        /// Call this function to do the PropertyChanged event.
        /// </summary>
        protected virtual void NotifyPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnDeserialized()
        {
        }

        protected virtual void OnDeserializing()
        {
        }

        #endregion Protected Methods

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SceneElement"/> class.
        /// </summary>
        public SceneElement()
        {
            parent = defaultParent;
            name = defaultName;
            enable = defaultEnable;
            children = new ObservableCollection<SceneElement>();
            effects = new ObservableCollection<Effect>();

            children.CollectionChanged += new NotifyCollectionChangedEventHandler(OnChildrenCollectionChanged);
            effects.CollectionChanged += new NotifyCollectionChangedEventHandler(OnEffectsCollectionChanged);
        }

        #endregion Public Constructors

        #region Public Events

        [Description("Called when scene tree has been changed."), Category("SharpGL")]
        public event SceneTreeChangeEventHandler SceneTreeChanged;

        [Description("Called when OpenGL context has been changed."), Category("SharpGL")]
        public event ContextChangeEventHandler ContextChanged;

        [Description("Called when properties has been changed."), Category("SharpGL")]
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Public Events

        #region Public Properties

        /// <summary>
        /// Gets the parent.
        /// </summary>
        //[Description("The parent scene element."), Category("Scene Element")]
        [Browsable(false)]
        public SceneElement Parent
        {
            get { return parent; }
            private set { parent = value; }
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        [Description("The object name."), Category("Scene Element")]
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value;

                    NotifyPropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is enabled; otherwise, <c>false</c>.
        /// </value>
        [Description("If true, the object participates in rendering."), Category("Scene Element")]
        public bool Enabled
        {
            get { return enable; }
            set
            {
                if (enable != value)
                {
                    enable = value;

                    NotifyPropertyChanged("Enable");
                }
            }
        }

        /// <summary>
        /// Gets the children.
        /// </summary>
        [Browsable(false)]
        public ObservableCollection<SceneElement> Children
        {
            get { return children; }
        }

        /// <summary>
        /// Gets the effects.
        /// </summary>
        //[Browsable(false)]
        [Description("The object effects."), Category("Scene Element")]
        [Editor(typeof(NETDesignSurface.Editors.EffectCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public ObservableCollection<Effect> Effects
        {
            get { return effects; }
        }

        /// <summary>
        /// Gets the joined bounding volumes of all child elements.
        /// </summary>
        [Browsable(false)]
        public BoundingVolume ChildrenBoundingVolume
        {
            get
            {
                BoundingVolume boundingVolume = null;
                IVolumeBound boundedElement;

                foreach (var childElement in Traverse())
                {
                    if (childElement is IVolumeBound)
                    {
                        boundedElement = childElement as IVolumeBound;
                        if (null == boundingVolume)
                            boundingVolume = new BoundingVolume(boundedElement.BoundingVolume.BoundingRect);
                        else
                            boundingVolume.Union(boundedElement.BoundingVolume);
                    }
                }

                return (boundingVolume == null ? new BoundingVolume() : boundingVolume);
            }
        }

        #endregion Public Properties
    }
}